
import os
import pathlib
import re
import shutil

default_types = set(['BOOL', 'SINT' , 'INT', 'DINT', 'LINT', 'USINT' , 'UINT', 'UDINT', 'ULINT',
                    'REAL', 'LREAL', 'TIME', 'DATE', 'TIME_OF_DAY ', 'TOD', 'DATE_AND_TIME', 'DT',
                    'STRING', 'BYTE', 'WORD', 'DWORD', 'LWORD', 'WSTRING',
                     'TON', 'TP', 'TOF', 'SR', 'RS',  'CTU', 'CTD', 'CTUD', 'RTC',
                     'R_TRIG', 'F_TRIG'])

class CommonOperator(object):
    def __init__(self):
        self.process_name = 'CommonOperator'
        self.file_infos = {}
        self.file_names = None
        # re.compile(r":([ \t]*)(\w+)(\s*)(;|:=|\()")
        self.patterns = [re.compile(r"[ \t]+:([ \t]*)(\w+)(\s*)(;|:=|\()"), re.compile(r"OF(\s*)(\w+)(;)")]

    def process(self):
        self.build_file_list_want_process() # 获取目录下所有要处理的文件的信息
        self.file_names = list(self.file_infos.keys())
        self.file_names.sort(reverse=True)
        self.parse_all_include_info() # 分析目录下所有文件，include的所有类型
        self.parse_all_includeed_count() # 对依赖关系进行计数

        # 根据依赖技术对文件排序
        self.file_names.sort(key=lambda v: self.file_infos[v].includeed_count, reverse=True)

        # 按照优先级顺序归并文件
        self.create_one_file()
        print("{} ---ok".format(self.process_name))

    def build_file_list_want_process(self):
        raise RuntimeError("子类必须实现方法: def build_file_list_want_process(self)")

    def build_file_info(self, path):
        """
        取出当前目录下的所有文件的信息 
        """
        result = {}
        for file_path in path.iterdir():
            if file_path.is_dir():
                # raise RuntimeError("不支持第三级子目录")
                result.update(self.build_file_info(file_path))
            else:
                result[file_path.name.split('.')[0].upper()] = STFileInfo(file_path)

        return result

    def parse_include_info(self, fileInfo):
        """
        分析出单个文件的include信息
        """
        with fileInfo.path.open('r', encoding='utf-8') as f:
            try:
                content = f.read()
            except UnicodeDecodeError as e:
                raise UnicodeDecodeError("file: {}, except: {}".format(fileInfo.path, e))
            
            for pattern in self.patterns:
                includes = pattern.findall(content)
                if includes:
                    for t in includes:
                        if t[1].upper() in default_types:
                            continue
                        if t[1].upper() in fileInfo.include_file:
                            continue
                        fileInfo.include_file.append(t[1].upper())

    def parse_all_include_info(self):
        """
        遍历分析出所有文件的include信息 
        """
        for key, value in self.file_infos.items():
            self.parse_include_info(value)

    def parse_includeed_count(self, fileInfo):
        """
        分析单个文件的依赖计数 
        """
        if len(fileInfo.include_file) == 0:
            return

        for filename in fileInfo.include_file:
            if filename in self.file_infos:
                self.file_infos[filename].includeed_count = self.file_infos[filename].includeed_count \
                                                            + fileInfo.includeed_count \
                                                            + 1
                self.parse_includeed_count(self.file_infos[filename])
            else:
               print("{} 使用了一个未知类型:{}".format(fileInfo.path,filename))

    def parse_all_includeed_count(self):
        """
        遍历分析所有的依赖计数  
        """
        for key, value in self.file_infos.items():
            self.parse_includeed_count(value)

    def create_one_file(self):
        """
        合并成一个整合的文件
        """
        if not self.combin_file.exists():
            self.combin_file.touch()

        with self.combin_file.open('w+', encoding='utf-8') as f:
            f.truncate()
            for fileName in self.file_names:
                with self.file_infos[fileName].path.open('r', encoding='utf-8') as rf:
                    f.write(rf.read())
                    f.write('\n\n')

class STFileInfo(object):
    def __init__(self, path):
        self.path = path
        self.includeed_count = 0
        self.include_file = []
    def __repr__(self):
        return "(file:{}, count:{}, include_files: {})".format(self.path.name, self.includeed_count, self.include_file)

class DataDirProcesser(CommonOperator):
    def __init__(self):
        super(DataDirProcesser, self).__init__()
        self.process_name = 'Data 目录'
        self.root_dir = pathlib.Path(os.getcwd()) / "DATA"
        if not self.root_dir.exists():
            raise RuntimeError("{} 不存在".format(self.root_dir))
        
        self.sub_dirs = [x for x in self.root_dir.iterdir() if x.is_dir()]
        if len(self.sub_dirs) == 0:
            raise RuntimeError("DATA 目录下为空")

        self.combin_file = self.root_dir / 'DATA.pt'

    def build_file_list_want_process(self):
        # data目录下处理AXis和CNC两个子目录
        for path in self.sub_dirs:
            self.file_infos.update(self.build_file_info(path))

class FunctionBblockDirProcesser(CommonOperator):
    def __init__(self):
        super(FunctionBblockDirProcesser, self).__init__()
        self.process_name = 'FunctionBlock 目录'
        self.root_dir = pathlib.Path(os.getcwd()) / "POUS" / "FUNCITONBLOCK"
        if not self.root_dir.exists():
            raise RuntimeError("{} 不存在".format(self.root_dir))

        self.combin_file = self.root_dir / 'FUNCTIONBLOCK.pt'

    def build_file_list_want_process(self):
        # function block 只处理单个目录
        self.file_infos.update(self.build_file_info(self.root_dir))


class FunctionDirProcesser(CommonOperator):
    def __init__(self):
        super(FunctionDirProcesser, self).__init__()
        self.process_name = 'Function 目录'
        self.root_dir = pathlib.Path(os.getcwd()) / "POUS" / "FUNCTION"
        if not self.root_dir.exists():
            raise RuntimeError("{} 不存在".format(self.root_dir))

        self.combin_file = self.root_dir / 'FUNCITON.pt'

    def build_file_list_want_process(self):
        self.file_infos.update(self.build_file_info(self.root_dir))

def comb_all_st_file():
    root_dir = pathlib.Path(os.getcwd())
    outfile = root_dir / "PLC" / "plc.st"
    if not outfile.exists():
        outfile.touch()

    data_file = root_dir / "DATA" / "DATA.pt"
    funcblock_file = root_dir/ "POUS" / "FUNCITONBLOCK" / "FUNCTIONBLOCK.pt"
    func_file = root_dir / "POUS" / "FUNCTION" /"FUNCITON.pt"
    main_file = root_dir / "POUS" / "MAIN" / "MAIN.ST"
    config_file = root_dir / "RESOURCE" / "CONFIG.ST"

    with outfile.open('w+', encoding='utf-8') as output_file:
        output_file.truncate()

        for filepath in [data_file, func_file, funcblock_file, main_file, config_file]:
            with filepath.open('r', encoding='utf-8') as input_file:
                output_file.write(input_file.read())
                output_file.write('\n\n')

def copy_st_file():
    root_dir = pathlib.Path(os.getcwd())
    newName = 'Compiler\st_source\plc.st'
    shutil.copyfile("PLC\plc.st", newName)

def compileST():
    copy_st_file()
    ret = os.chdir('./Compiler')
    ret = os.system('python run.py')

def delete_all_st_file():
    root_dir = pathlib.Path(os.getcwd())
    data_file = root_dir / "DATA" / "DATA.pt"
    funcblock_file = root_dir / "POUS" / "FUNCITONBLOCK" / "FUNCTIONBLOCK.pt"
    func_file = root_dir / "POUS" / "FUNCTION" / "FUNCITON.pt"
    outfile = root_dir / "PLC" / "plc.st"
    CompilerFile = root_dir/ "Compiler" / "st_source" / "plc.st"
    for path in [CompilerFile, outfile, func_file, funcblock_file, data_file]:
        if path.exists():
            path.unlink()

if __name__ == "__main__":
    delete_all_st_file()


    print("生成子模块")
    data = DataDirProcesser()
    data.process()
    default_types.update(data.file_names)

    func = FunctionDirProcesser()
    func.process()
    default_types.update(func.file_names)

    funcblock = FunctionBblockDirProcesser()
    funcblock.process()
    default_types.update(funcblock.file_names)

    print("归并大st")
    comb_all_st_file()

    print("开始编译")
    compileST()

